﻿using System;
using System.Collections.Generic;
using System.Linq;
using Cms.Domain;
using Cms.Domain.RepositoryInterfaces;
using Cms.Utilities;
using NHibernate;
using NHibernate.Criterion;

namespace Cms.Data
{
    public class ContentRepository : BaseRepository, IContentRepository
    {
        public ContentRepository()
        {
        }

        public ContentRepository(ISession session)
            : base(session)
        {
        }

        #region IContentRepository Members

        public Content GetContent(int id)
        {
            return Session.Get<Content>(id);
        }
        
        public ContentTranslationVersion GetContentTranslationVersion(int id)
        {
            return Session.Get<ContentTranslationVersion>(id);
        }

        public IList<ContentTranslationVersion> GetContentWithReferencesTo(string virtualPath, int siteId)
        {
            string hql = "from ContentTranslationVersion where Translation.Content.Site.ID = :siteID and Html like :html";
            IList<ContentTranslationVersion> versions = Session.CreateQuery(hql)
                .SetInt32("siteID", siteId)
                .SetString("html", "%" + virtualPath.Replace("'", "''").Replace("[", "_").Replace("]", "_") + "%")
                .SetCacheable(true)
                .List<ContentTranslationVersion>();
            return versions;
        }

        public void DeleteContent(int id)
        {
            ITransaction tx = null;
            try
            {
                tx = Session.BeginTransaction();
                Content c = GetContent(id);
                foreach (var kvp in c.Translations)
                {
                    foreach (ContentTranslationVersion ctv in kvp.Value.Versions)
                    {
                        int i;
                        foreach (PageContentSet pcs in ctv.PageContentSets)
                        {
                            if (pcs.PublishedVersion != null && pcs.PublishedVersion.ID == ctv.ID)
                                pcs.PublishedVersion = null;
                            i = 0;
                            while (i < pcs.TranslationVersions.Count)
                            {
                                if (pcs.TranslationVersions[i].ID == ctv.ID)
                                    pcs.TranslationVersions.RemoveAt(0);
                                else
                                    i++;
                            }
                        }
                        i = 0;
                        while (i < ctv.PageContentSets.Count)
                        {
                            if (ctv.PageContentSets[i].TranslationVersions.Count == 0)
                            {
                                if (ctv.PageContentSets[i].Page.ContentSets.ContainsKey(kvp.Key) &&
                                    ctv.PageContentSets[i].Page.ContentSets[kvp.Key].ID == ctv.PageContentSets[i].ID)
                                    ctv.PageContentSets[i].Page.ContentSets.Remove(kvp.Key);
                                ctv.PageContentSets.RemoveAt(0);
                            }
                            else
                                i++;
                        }
                        foreach (MasterTemplateContentSet mtcs in ctv.MasterTemplateContentSets)
                        {
                            if (mtcs.PublishedVersion != null && mtcs.PublishedVersion.ID == ctv.ID)
                                mtcs.PublishedVersion = null;
                            i = 0;
                            while (i < mtcs.TranslationVersions.Count)
                            {
                                if (mtcs.TranslationVersions[i].ID == ctv.ID)
                                    mtcs.TranslationVersions.RemoveAt(0);
                                else
                                    i++;
                            }
                        }
                        i = 0;
                        while (i < ctv.MasterTemplateContentSets.Count)
                        {
                            if (ctv.MasterTemplateContentSets[i].TranslationVersions.Count == 0)
                            {
                                if (ctv.MasterTemplateContentSets[i].MasterTemplate.ContentSets.ContainsKey(kvp.Key) &&
                                    ctv.MasterTemplateContentSets[i].MasterTemplate.ContentSets[kvp.Key].ID ==
                                    ctv.MasterTemplateContentSets[i].ID)
                                    ctv.MasterTemplateContentSets[i].MasterTemplate.ContentSets.Remove(kvp.Key);
                                ctv.MasterTemplateContentSets.RemoveAt(0);
                            }
                            else
                                i++;
                        }
                        foreach (DetailTemplateContentSet dtcs in ctv.DetailTemplateContentSets)
                        {
                            if (dtcs.PublishedVersion != null && dtcs.PublishedVersion.ID == ctv.ID)
                                dtcs.PublishedVersion = null;
                            i = 0;
                            while (i < dtcs.TranslationVersions.Count)
                            {
                                if (dtcs.TranslationVersions[i].ID == ctv.ID)
                                    dtcs.TranslationVersions.RemoveAt(0);
                                else
                                    i++;
                            }
                        }
                        i = 0;
                        while (i < ctv.DetailTemplateContentSets.Count)
                        {
                            if (ctv.DetailTemplateContentSets[i].TranslationVersions.Count == 0)
                            {
                                if (ctv.DetailTemplateContentSets[i].DetailTemplate.ContentSets.ContainsKey(kvp.Key) &&
                                    ctv.DetailTemplateContentSets[i].DetailTemplate.ContentSets[kvp.Key].ID ==
                                    ctv.DetailTemplateContentSets[i].ID)
                                    ctv.DetailTemplateContentSets[i].DetailTemplate.ContentSets.Remove(kvp.Key);
                                ctv.DetailTemplateContentSets.RemoveAt(0);
                            }
                            else
                                i++;
                        }
                    }
                }
                foreach (Category cc in c.Categories)
                    if (cc.ContainsContent(c))
                        cc.Content.Remove(c);
                c.Categories.Clear();
                Session.SaveOrUpdate(c);
                Session.SaveOrUpdate(c.Site);
                Session.Delete("from Content c where c.ID = " + id);
                Session.Flush();
                tx.Commit();
            }
            catch (Exception ex)
            {
                RollbackTransaction("ContentRepository.DeleteContent", tx, ex);
                throw;
            }
        }

        public IList<Content> ListContent(SearchContentFilter filter, int skipFirstCount, int returnCount, IList<int> categoryIDs)
        {
            string hql = "from Content c " +
                            "join fetch c.Translations t " +
                            "join fetch t.PublishedVersion v " +
                            "where exists (from c.Categories cc where cc.ID in (:categoryIDs)) " +
                            "and (c.StartPublishing = null or c.StartPublishing <= current_timestamp) " +
                            "and (c.EndPublishing = null or c.EndPublishing >= current_timestamp) " +
                            "and t.Locale.ID = :localeID " +
                            "and t.PublishedVersion != null " +
                            "order by ";
            switch (filter.SortMode)
            {
                case ContentSortMode.PublishedTimestamp:
                    hql += "t.LatestPublished";
                    break;
                case ContentSortMode.SortKey:
                    hql += "c.SortKey";
                    break;
            }
            if (filter.SortDirection == SortDirection.Descending)
            {
                hql += " desc";
            }
            var query = Session.CreateQuery(hql)
                                .SetParameter("localeID", filter.LocaleId)
                                .SetParameterList("categoryIDs", categoryIDs.ToList());
            if (returnCount > 0)
            {
                query = query.SetMaxResults(returnCount);
            }
            if (skipFirstCount > 0)
            {
                query = query.SetFirstResult(skipFirstCount);
            }
            query.SetCacheable(true);
            return query.List<Content>();
        }

        public IList<Content> SearchContent(ref SearchContentFilter filter, int siteID, IList<int> categoryIDs)
        {
            // Start by getting the count in a separate query
            string hql = "select count(*) from Content c left join c.Translations t" + BuildSearchContentWhereClause(filter);
            IQuery rowCountQuery = Session.CreateQuery(hql);
            rowCountQuery.SetInt32("localeID", filter.LocaleId);
            rowCountQuery.SetInt32("siteID", siteID);
            if (filter.CategoryId > 0)
            {
                rowCountQuery.SetParameterList("categoryIDs", categoryIDs.ToList());
            }
            if (!string.IsNullOrEmpty(filter.ContentName))
            {
                rowCountQuery.SetString("contentName", "%" + filter.ContentName + "%");
            }
            filter.Count = (int)((long)rowCountQuery.UniqueResult());

            // Continue by getting the actual result
            hql = "from Content c left join fetch c.Translations t" + BuildSearchContentWhereClause(filter) + " order by ";
            switch (filter.SortMode)
            {
                case ContentSortMode.PublishedTimestamp:
                    hql += "c.FirstPublished";
                    break;
                case ContentSortMode.SortKey:
                    hql += "c.SortKey";
                    break;
            }
            if (filter.SortDirection == SortDirection.Descending)
            {
                hql += " desc";
            }
            IQuery resultQuery = Session.CreateQuery(hql);
            resultQuery.SetInt32("localeID", filter.LocaleId);
            resultQuery.SetInt32("siteID", siteID);
            if (filter.CategoryId > 0)
            {
                resultQuery.SetParameterList("categoryIDs", categoryIDs.ToList());
            }
            if (!string.IsNullOrEmpty(filter.ContentName))
            {
                resultQuery.SetString("contentName", "%" + filter.ContentName + "%");
            }
            return resultQuery
                .SetMaxResults(filter.PageSize)
                .SetFirstResult(filter.CurrentPage * filter.PageSize)
                .List<Content>();
        }
        
        public void DeleteScheduledPublishing(int id)
        {
            DeleteHql("from ScheduledPublishing where Id = " + id, "ContentRepository.DeleteScheduledPublishing");
        }
        
        public IList<ScheduledPublishing> GetScheduledPublishings(int siteID)
        {
            string hql =
                "from ScheduledPublishing sp where sp.ContentTranslationVersion.Translation.Content.Site.ID = :currentSiteID order by sp.Timestamp asc";
            return Session.CreateQuery(hql)
                .SetInt32("currentSiteID", siteID)
                .List<ScheduledPublishing>();
        }
        
        public void ExecuteScheduledPublishings()
        {
            string hql = "from ScheduledPublishing sp where sp.Timestamp < :now order by sp.Timestamp asc";
            IList<ScheduledPublishing> publishings = Session.CreateQuery(hql)
                .SetString("now", DateTime.Now.Ticks.ToString())
                .List<ScheduledPublishing>();
            foreach (ScheduledPublishing sp in publishings)
            {
                sp.ContentTranslationVersion.Publish();
                Logging.LogInfo("Scheduled publishing of version #" + sp.ContentTranslationVersion.ID + " completed");
                sp.ContentTranslationVersion.ScheduledPublishings.Remove(sp);
                sp.ContentTranslationVersion = null;
                DeleteScheduledPublishing(sp.ID);
            }
        }

        public RssFeed GetRssFeed(int siteId, string filename)
        {
            string hql = "from RssFeed rss where rss.Filename = :filename and rss.Site.ID = :siteID";
            IList<RssFeed> rssFeeds = Session.CreateQuery(hql)
                                        .SetString("filename", filename)
                                        .SetInt32("siteID", siteId)
                                        .List<RssFeed>();
            return rssFeeds.Count == 1 ? rssFeeds[0] : null;
        }

        public RssFeed GetRssFeed(int id)
        {
            return Session.Get<RssFeed>(id);
        }

        #endregion

        private string BuildSearchContentWhereClause(SearchContentFilter filter)
        {
            string hql = " where c.Site.ID = :siteID and (size(c.Translations) = 0 or t.Locale.ID = :localeID)";
            // Reminder: [c] is an alias for Content!
            if (filter.CategoryId > 0)
            {
                hql += " and exists (from c.Categories cc where cc.ID in (:categoryIDs))";
            }
            if (!string.IsNullOrEmpty(filter.ContentName))
            {
                hql += " and c.Name like :contentName";
            }
            return hql;
        }
    }
}